/* 
 * Shuriken is the legal property of its developers. Please refer to the
 * COPYRIGHT file distributed with this
 * source distribution.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using Shuriken.Base;

namespace Shuriken.Core.Store
{
    /// <summary>
    /// Exception occuring during type registration
    /// </summary>
    public class SubjectRegistrationException : Exception
    {
        public SubjectRegistrationException(string msg)
            : base(msg)
        {
        }

        public SubjectRegistrationException(string msg, Exception innerException)
            : base(msg, innerException)
        {
        }
    }

    public class PluginActionManager
    {
        public static PluginActionManager m_default;
        public List<IAction> m_actionList = new List<IAction>();

        public static PluginActionManager DefaultInstance
        {
            get
            {
                if (m_default == null)
                {
                    m_default = new PluginActionManager();
                }

                return m_default;
            }
        }

        public void AddAction(IAction action)
        {
            m_actionList.Add(action);
        }

        public List<IAction> GetListOfActions(ISubject subject)
        {
            List<IAction> actions = new List<IAction>();

            foreach (IAction action in m_actionList)
            {
                if (action.CanExecuteOn(subject))
                {
                    actions.Add(action);
                }
            }

            return actions; 
        }
    }

    public class ActionManager
    {
        private static ActionManager m_default;
        private Dictionary<Type, List<IAction>> m_actionList = new Dictionary<Type, List<IAction>>();
        private Dictionary<Type, bool> m_typeRegistry = new Dictionary<Type, bool>(); 

        public static ActionManager GetDefaultInstance()
        {
            if (m_default == null)
            {
                m_default = new ActionManager();
            }

            return m_default; 
        }

        /// <summary>
        /// Removes all the actions registered
        /// </summary>
        public void ResetActions()
        {
            m_actionList = new Dictionary<Type, List<IAction>>();
            m_typeRegistry = new Dictionary<Type, bool>();
        }

        #region Safe Registration Facilities 

        /// <summary>
        /// Decides whether IStoreItem is implemented for the give type.
        /// </summary>
        /// <param name="type">type</param>
        /// <returns>true, if IStoreItem implemented, false otherwise</returns>
        public bool IsStoreItemInterfaceImplemented(Type type)
        {
            Type[] inta = type.GetInterfaces();

            foreach (Type t in inta)
            {
                if(t.Equals(typeof(IStoreItem)))
                {
                    return true; 
                }
            }

            return false; 
        }

        /// <summary>
        /// Registers the subject to be handled by the action manager. 
        /// </summary>
        /// <param name="type"></param>
        public void RegisterSubjectType(Type type)
        {
            if (!IsStoreItemInterfaceImplemented(type))
            {
                throw new SubjectRegistrationException("Type needs to implement IStoreItem to be allowed to register!");
            }

            if(!m_typeRegistry.ContainsKey(type))
            {
                m_typeRegistry.Add(type, false);
                Type baseType = type.BaseType;

                if (IsStoreItemInterfaceImplemented(baseType) && !m_typeRegistry.ContainsKey(baseType))
                {
                    RegisterSubjectType(baseType); 
                }
            }
        }


        /// <summary>
        /// Tells if the type was registered as subject 
        /// </summary>
        /// <param name="type">subject type</param>
        /// <returns>true if the subject was registered</returns>
        public bool IsSubjectTypeRegistered(Type type)
        {
            return m_typeRegistry.ContainsKey(type);
        }

        /// <summary>
        /// Tells if the actions for the Subject were already registered
        /// </summary>
        /// <param name="type">subject type</param>
        /// <returns>true if the actions were already registered for this subject</returns>
        private bool IsActionRegistRationDone(Type type)
        {
            bool registered;

            if (m_typeRegistry.TryGetValue(type, out registered))
            {
                return registered; 
            }

            return false;
        }

        /// <summary>
        /// Registers all the actions needed by the subject, walks the object graph 
        /// </summary>
        /// <param name="type">Type to Safe Register</param>
        /// <remarks>
        /// Safe registration is going to walk invoke itself on the parent and see if that was 
        /// already safe registered, it does this in a recursive way so make sure first that 
        /// all the parent classes has been self registered
        /// After making sure that this happened to all the parent class then it instatietes
        /// the type an calls the appropriate call to register the actions for this type 
        /// </remarks>
        private void SafeRegisterActionsForSubject(Type type)
        {
            // Checking if the type was registered at all
            if (!IsSubjectTypeRegistered(type) && IsStoreItemInterfaceImplemented(type))
            {
                RegisterSubjectType(type);
            }

            // Checking if this type is not the root type 
            if (!IsStoreItemInterfaceImplemented(type))
            {
                return;
            }

            // Registering actions if it was not finished before
            if(!IsActionRegistRationDone(type))
            {
                Type baseType = type.BaseType;
                SafeRegisterActionsForSubject(baseType);

                // Instantiate the type and call the methods needed to register actions. 
                ConstructorInfo consInfo = type.GetConstructor(Type.EmptyTypes);
                object obj = consInfo.Invoke(new Object[0]);

                IStoreItem baseItem = (IStoreItem)obj;
                baseItem.RegisterParentActions();
                baseItem.RegisterActions(); 

                m_typeRegistry.Remove(type);
                m_typeRegistry.Add(type, true);
            }

        }
        #endregion


        #region Action Handling 
        /// <summary>
        /// Register an action for the specified type. 
        /// </summary>
        /// <param name="typeOfSubject">type to register the action for</param>
        /// <param name="action">action to register</param>
        public void RegisterAction(Type typeOfSubject, IAction action)
        {
            if (!m_actionList.ContainsKey(typeOfSubject))
            {
                m_actionList.Add(typeOfSubject, new List<IAction>());
            }

            // Let's see if we already registered this action for this type. 
            for (int loop = 0; loop < m_actionList[typeOfSubject].Count; loop++)
            {
                if (m_actionList[typeOfSubject][loop].Name == action.Name)
                {
                    m_actionList[typeOfSubject][loop] = action;
                    return; 
                }
            }

            // Adding the new action, if the action is a default action and there are 
            // other actions in the list then we need to make sure that the default action 
            // will be the first in this list
            if (action.IsDefault && m_actionList[typeOfSubject].Count > 0)
            {
                m_actionList[typeOfSubject].Insert(0, action);
            }
            else
            {
                m_actionList[typeOfSubject].Add(action);
            }
        }

        /// <summary>
        /// Registers a list of actions 
        /// </summary>
        /// <param name="typeOfSubject">type to register the actions for</param>
        /// <param name="listOfActions">list of actions to register</param>
        public void RegisterActions(Type typeOfSubject, List<IAction> listOfActions)
        {
            foreach (IAction action in listOfActions)
            {
                RegisterAction(typeOfSubject, action);
            }
        }

        /// <summary>
        /// List of actions available for the kind of subject type 
        /// </summary>
        /// <param name="typeOfSubject">subject type</param>
        /// <param name="actionList">list of actions available</param>
        public void GetListOfActions(Type typeOfSubject, List<IAction> actionList)
        {
            SafeRegisterActionsForSubject(typeOfSubject);

            if (m_actionList.ContainsKey(typeOfSubject))
            {
                List<IAction> actions = m_actionList[typeOfSubject];
                actionList.AddRange(actions);
            }
        }

        public void GetListOfActions(ISubject subject, List<IAction> actionList)
        {
            if(subject == null)
            {
                return; 
            }

            List<IAction> actions = new List<IAction>();

            GetListOfActions(subject.GetType(), actions);

            List<IAction> pluginActions = PluginActionManager.DefaultInstance.GetListOfActions(subject);

            actionList.AddRange(actions);
            actionList.AddRange(pluginActions);
        }

        /// <summary>
        /// Retrieves the action with the given name for this type
        /// </summary>
        /// <param name="typeOfSubject">type of subject</param>
        /// <param name="name">name of action</param>
        /// <returns>action</returns>
        public IAction GetActionByName(Type typeOfSubject, string name)
        {
            List<IAction> actions = new List<IAction>();
            GetListOfActions(typeOfSubject, actions);

            foreach (IAction action in actions)
            {
                if (action.Name.Equals(name))
                {
                    return action;
                }
            }

            return null;
        }

        /// <summary>
        /// Removes an action for a type
        /// </summary>
        /// <param name="typeOfSubject">type where the action registered</param>
        /// <param name="action">action to remove</param>
        /// <returns>true, if removed, removed if not found or type not available</returns>
        public bool RemoveAction(Type typeOfSubject, IAction action)
        {
            if(m_actionList[typeOfSubject] != null)
            {
                List<IAction> actions = m_actionList[typeOfSubject];
                actions.Remove(action);
                return true;
            }

            return false; 
        }

        /// <summary>
        /// Removes an action for a type
        /// </summary>
        /// <param name="typeOfSubject">type where the action registered</param>
        /// <param name="actionName">name of action to remove</param>
        /// <returns>true, if removed, removed if not found or type not available</returns>
        public bool RemoveAction(Type typeOfSubject, string actionName)
        {
            if(m_actionList[typeOfSubject] != null)
            {
                foreach (IAction action in m_actionList[typeOfSubject])
                {
                    if (action.Name.Equals(actionName))
                    {
                        return RemoveAction(typeOfSubject, action);
                    }
                }
            }

            return false; 
        }

        /// <summary>
        /// Returns the default action
        /// </summary>
        /// <param name="typeOfSubject">subject type</param>
        /// <returns>default action</returns>
        public IAction GetDefaultAction(Type typeOfSubject)
        {
            List<IAction> actions = new List<IAction>();
            GetListOfActions(typeOfSubject, actions);

            foreach (IAction action in actions)
            {
                if (action.IsDefault)
                {
                    return action; 
                }
            }

            throw new Exception("No default action registered for type : " + typeOfSubject.ToString());
        }

        #endregion
    }
}
